package com.kamistoat.meimeimall.common.annotation.annotationHandler;

import com.alibaba.fastjson.JSON;
import com.kamistoat.meimeimall.common.annotation.WithCache;
import com.kamistoat.meimeimall.common.exception.BizCodeEnum;
import com.kamistoat.meimeimall.common.exception.RRException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 处理 {@link com.kamistoat.meimeimall.common.annotation.WithCache} 注解.
 * 注意，由于 common 模块不具备主启动类，也就无法自动扫描 Component.
 * 因此需要手动在 SPI 文件中指定这些 Component 进行注入.
 * <p></p>
 * 注解属性示例: member:receiveAddress:#memberId:#isDefault.
 * <p>.
 * 顺序-50，比全局登录拦截器 {@link com.kamistoat.meimeimall.common.interceptor.GlobalLoginInterceptor} 级别低.</p>
 */
@Aspect
@Component
@Order(-50)
public class WithCacheHandler {
    protected Logger logger = LoggerFactory.getLogger(WithCacheHandler.class);

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedissonClient redissonClient;

    @Around(value = "@annotation(withCache)")
    public Object innerAround(ProceedingJoinPoint proceedingJoinPoint, WithCache withCache) throws Throwable {
        // 切面方法
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        // 切面方法输入参数名称
        String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
        // 切面方法输入参数的元类
        Class[] paramTypes = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterTypes();
        // 切面方法输入参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();

        /**
         * 拼接缓存key
         */
        StringBuilder sb = new StringBuilder();
        assert !withCache.prefix().equals("");
        String[] prefixes = withCache.prefix().split(":");
        for (int index = 0; index < prefixes.length; index++) {
            // 寻找到自定义部分
            if (prefixes[index].charAt(0) == '#') {
                // 可能是单层 id，也可能是嵌套 memberEntity.id
                String paramName = prefixes[index].substring(1);
                // 单层
                if (paramName.indexOf('.') == -1) {
                    for (int paramIndex = 0; paramIndex < paramNames.length; paramIndex++) {
                        if (paramNames[paramIndex].equals(paramName)) {
                            prefixes[index] = String.valueOf(paramValues[paramIndex]);
                            break;
                        }
                    }
                }
                // 嵌套
                else {
                    String[] nestedParam = paramName.split("\\.");
                    String upperParamName = nestedParam[0];
                    for (int paramIndex = 0; paramIndex < paramNames.length; paramIndex++) {
                        // 首先找到输入参数中的对应变量
                        if (paramNames[paramIndex].equals(upperParamName)) {
                            // 开始向下寻找最终的getter方法
                            Object invoke = paramValues[paramIndex];
                            Class<?> aClass = paramTypes[paramIndex].newInstance().getClass();
                            for (int in = 1; in < nestedParam.length; in++) {
                                String lowerParamGetter = "get" + nestedParam[in].substring(0, 1).toUpperCase() + nestedParam[in].substring(1);
                                invoke = aClass.getMethod(lowerParamGetter).invoke(invoke);
                                aClass = aClass.getMethod(lowerParamGetter).getReturnType();
                            }
                            prefixes[index] = String.valueOf(invoke);
                            break;
                        }
                    }
                }
            }
            sb.append(prefixes[index]).append(":");
        }

        String prefix = sb.substring(0, sb.length() - 1);
        // 用于解决缓存击穿的分布式锁
        String lockName = sb.append("redis:through:lock").toString();

        while (true) {
            /**
             * 优先返回缓存中查询结果
             */
            String redisResult = stringRedisTemplate.opsForValue().get(prefix);
            if (redisResult != null) {
                // logger.info("命中缓存:" + prefix);
                return withCache.isList() ?
                        JSON.parseArray(redisResult, withCache.returnType()) : JSON.parseObject(redisResult, withCache.returnType());
            }
            /**
             * 缓存未命中，则抢占分布式锁
             */
            RLock searchLock = redissonClient.getLock(lockName);
            try {
                boolean hasLock = searchLock.tryLock();
                if (hasLock) {
                    logger.info("放行缓存构建:" + lockName);
                    /**
                     * 抢占到分布式锁，则执行切面的查询方法
                     */
                    Object proceedResult = proceedingJoinPoint.proceed();
                    /**
                     * 切面查询结果入缓存
                     */
                    stringRedisTemplate.opsForValue().set(prefix, JSON.toJSONString(proceedResult));
                    /**
                     * 返回切面查询结果
                     */
                    return proceedResult;
                } else {
                    /**
                     * 没抢到分布式锁，睡眠200ms后重新去缓存中查。
                     * 当然这里可能导致后端阻塞。实际生产中，应该直接向前端返回响应，由前端重新执行查询，而不是阻塞在后端。
                     */
                    Thread.sleep(200);
                }
            } catch (Exception exception) {
                throw new RRException(exception.getMessage(), BizCodeEnum.UNKNOW_EXCEPTION.getCode());
            } finally {
                searchLock.unlock();
            }
        }
    }
}
